﻿/*================================================================================
 * DA6502 ： 6502处理器反汇编工具
 * 
 * Copyright (C) 2022 Wei.ZHOU. 版权保留.
 * http://da6502.syuui.top
 ================================================================================*/

using DA6502.Controls;
using DA6502.CPU;
using DA6502.IO;
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;

namespace DA6502
{
    public partial class FrmMain : Form
    {
        private enum SYSTEM_STATUS : byte
        {
            INIT = 0,
            FILE_OPENED = 1
        }

        #region ==============================| 类属性 |==============================

        /// <summary>
        /// 打开的文件名称
        /// </summary>
        private string FilePath = String.Empty;

        /// <summary>
        /// 存储R6A文件名称
        /// </summary>
        private string SaveFilePath = String.Empty;

        /// <summary>
        /// MOS6502 CPU
        /// </summary>
        private CPU.CPU cpu;

        /// <summary>
        /// 子程序列表窗体
        /// </summary>
        private FrmEntries frmEntries;

        /// <summary>
        /// 是否含有脏数据（修改未保存的数据）
        /// </summary>
        public bool Dirty
        {
            get
            {
                dirty = memory.Dirty || frmEntries.Dirty;
                return dirty;
            }
            set
            {
                dirty = value;
                if (dirty) Text += " *";
                else Text = Properties.Resources.ProjectName + " " + SaveFilePath == String.Empty ? FilePath : SaveFilePath;
            }
        }
        private bool dirty = false;
        #endregion

        #region ==============================| 初始化相关 |==============================

        public FrmMain()
        {
            InitializeComponent();
            this.Text = Properties.Resources.ProjectName;

            cpu = new CPU.CPU(memory);

            cpu.OnPcChange += OnCpuPcChangeHandler;
            cpu.OnRegisterChange += OnCpuRegisterChangeHandler;
            regPanel.OnPcChange += OnPanelPcChangeHandler;
            regPanel.OnRegisterChange += OnPanelRegisterChangeHandler;

            memory.ReverseSingleLine += ReverseSingleLine;
            memory.ReverseMultiLine += ReverseMultiLine;

            regPanel.PC = 0;
            regPanel.A = 0;
            regPanel.X = 0;
            regPanel.P = 0x24;
            regPanel.S = 0xFD;

            frmEntries = new FrmEntries(this);
            frmEntries.ReverseSingle += ReverseSingleLine;
            frmEntries.ReverseMulti += ReverseMultiLine;
        }


        #endregion

        #region ==============================| 回调函数 |==============================

        /// <summary>
        /// 文件 - 退出 菜单回调
        /// </summary>
        private void Quit(object sender, EventArgs e)
        {
            Application.Exit();
        }

        /// <summary>
        /// 文件 - 打开 菜单回调
        /// </summary>
        private void OpenFile(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = "存档文件(*.r6a)|*.r6a|二进制文件(*.bin)|*.bin|NES映像文件(*.nes)|*.nes";
            ofd.Multiselect = false;
            ofd.CheckFileExists = true;
            ofd.CheckPathExists = true;
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                FilePath = ofd.FileName;
                this.Text = Properties.Resources.ProjectName + " " + FilePath;
                mnuClose.Enabled = true;

                switch (Path.GetExtension(FilePath).ToLower())
                {
                    case ".nes":
                        OpenNesFile(FilePath);
                        break;
                    case ".bin":
                        OpenBinFile(FilePath);
                        break;
                    case ".r6a":
                        OpenR6aFile(FilePath);
                        break;
                    default:
                        MessageBox.Show("不支持此扩展名的文件");
                        break;
                }

                ToggleMenu(SYSTEM_STATUS.FILE_OPENED);

                ushort ad = (ushort)(memory[0xFFFA] | (memory[0xFFFB] << 8));
                frmEntries.SetEntry(ad, "NMI中断入口");
                ad = (ushort)(memory[0xFFFE] | (memory[0xFFFF] << 8));
                frmEntries.SetEntry(ad, "IRQ中断入口");
                ad = (ushort)(memory[0xFFFC] | (memory[0xFFFD] << 8));
                frmEntries.SetEntry(ad, "Reset中断入口");

                memory.JumpTo(ad);
            }
        }

        /// <summary>
        /// 文件 - 关闭 菜单回调
        /// </summary>
        private void CloseFile(object sender, EventArgs e)
        {
            if (!CheckDirty())
            {
                ToggleMenu(SYSTEM_STATUS.INIT);
                frmEntries?.Close();
                FilePath = String.Empty;
                mnuClose.Enabled = false;
                memory.Clear();
            }
        }

        private void MnuSingle_Click(object sender, EventArgs e) => memory.CmiSingle_Click(sender, e);

        private void MnuBatch_Click(object sender, EventArgs e) => memory.CmiBatch_Click(sender, e);

        private void MnuMarkAsU_Click(object sender, EventArgs e) => memory.CmiMarkAsU_Click(sender, e);

        private void MnuMarkAsI_Click(object sender, EventArgs e) => memory.CmiMarkAsI_Click(sender, e);

        private void MnuMarkAsO_Click(object sender, EventArgs e) => memory.CmiMarkAsO_Click(sender, e);

        private void MnuMarkAsV_Click(object sender, EventArgs e) => memory.CmiMarkAsV_Click(sender, e);

        private void MnuMarkAsA_Click(object sender, EventArgs e) => memory.CmiMarkAsA_Click(sender, e);

        private void MnuMarkAsS_Click(object sender, EventArgs e) => memory.CmiMarkAsS_Click(sender, e);

        private void MnuMarkAsP_Click(object sender, EventArgs e) => memory.CmiMarkAsP_Click(sender, e);

        /// <summary>
        /// RegPanel中的PC值变化事件
        /// </summary>
        /// <param name="v">PC的值</param>
        private void OnPanelPcChangeHandler(ushort v)
        {
            cpu.CallbackEnable = false;
            cpu.SetRegisterWord(REGISTER_WORD.PC, v);
            cpu.CallbackEnable = true;
        }

        /// <summary>
        /// RegPanel中的寄存器值变化事件
        /// </summary>
        /// <param name="r">寄存器名称</param>
        /// <param name="v">值</param>
        private void OnPanelRegisterChangeHandler(REGISTER_BYTE r, byte v)
        {
            cpu.CallbackEnable = false;
            cpu.SetRegisterByte(r, v);
            cpu.CallbackEnable = true;
        }

        /// <summary>
        /// CPU中的PC值变化事件
        /// </summary>
        /// <param name="v">PC的值</param>
        private void OnCpuPcChangeHandler(ushort v)
        {
            regPanel.CallbackEnable = false;
            regPanel.PC = v;
            regPanel.CallbackEnable = true;
        }

        /// <summary>
        /// CPU中的寄存器值变化事件
        /// </summary>
        /// <param name="r">寄存器名称</param>
        /// <param name="v">值</param>
        private void OnCpuRegisterChangeHandler(REGISTER_BYTE r, byte v)
        {
            regPanel.CallbackEnable = false;
            switch (r)
            {
                case REGISTER_BYTE.A: regPanel.A = v; break;
                case REGISTER_BYTE.X: regPanel.X = v; break;
                case REGISTER_BYTE.Y: regPanel.Y = v; break;
                case REGISTER_BYTE.S: regPanel.S = v; break;
                case REGISTER_BYTE.P: regPanel.P = v; break;
            }
            regPanel.CallbackEnable = true;
        }

        /// <summary>
        /// 菜单 文件 - 保存 回调
        /// </summary>
        private void SaveFile(object sender, EventArgs e)
        {
            if (SaveFilePath.Length == 0) SaveFileAs(sender, e);
            else SaveR6AFile();
        }

        /// <summary>
        /// 菜单 文件 - 另存为 回调
        /// </summary>
        private void SaveFileAs(object sender, EventArgs e)
        {
            SaveFileDialog sfd = new SaveFileDialog();
            sfd.AddExtension = true;
            sfd.CheckFileExists = false;
            sfd.Filter = "DA6502存档文件(*.d6s)|*.d6s";
            if (sfd.ShowDialog() == DialogResult.OK)
            {
                SaveFilePath = sfd.FileName;
                SaveR6AFile();
            }
        }

        /// <summary>
        /// 设置内存起始地址
        /// </summary>
        /// <param name="ad">内存起始地址</param>
        private void SetBaseAddr(ushort ad) => memory.BaseAddr = ad;

        /// <summary>
        /// 菜单 内存 - 跳转至 回调
        /// </summary>
        private void MnuJumpTo_Click(object sender, EventArgs e)
        {
            DlgJumpTo dlg = new DlgJumpTo();
            dlg.JumpToAddress += JumpTo;
            dlg.ShowDialog(this);
        }

        /// <summary>
        /// 菜单 窗口 - 子程序列表 回调
        /// </summary>
        private void MnuEntries_Click(object sender, EventArgs e)
        {
            if (mnuEntries.Checked)
            {
                frmEntries.Hide();
                mnuEntries.Checked = false;
            }
            else
            {
                frmEntries.Show();
                mnuEntries.Checked = true;
            }
        }

        /// <summary>
        /// 跳转至某地址
        /// </summary>
        /// <param name="ad">目标地址</param>
        public void JumpTo(ushort ad) => memory.JumpTo(ad);

        /// <summary>
        /// 窗体关闭事件<br></br>
        /// 同时需要关闭其余子窗体（FrmEntries）
        /// </summary>
        private void OnClose(object sender, FormClosingEventArgs e)
        {
            if (!CheckDirty())
            {
                frmEntries?.Close();
            }
            else
            {
                e.Cancel = true;
            }
        }
        #endregion

        #region ==============================| 反汇编 |==============================

        /// <summary>
        /// 反汇编用户指定的一行
        /// </summary>
        private void ReverseSingleLine(ushort ad)
        {
            cpu.SetRegisterWord(CPU.REGISTER_WORD.PC, ad);
            JumpTo(ad);
            byte cmd = cpu.DisassemblySingleStep();
            if (cmd == 0x20)
            {
                frmEntries.SetEntry(ad);
            }
        }

        /// <summary>
        /// 从用户指定的行开始反汇编，直到遇到以下命令：<br></br>
        /// - 跳转命令：JMP, JSR, RTI, RTS<br></br>
        /// - 分支命令：BCC,BCS, BEQ,BNE, BMI,BPL, BVC,BVS<br></br>
        /// 或者，当程序指针大于等于0xFFFA时也停止（0xFFFA - 0xFFFF为6502的中断向量表）
        /// </summary>
        private void ReverseMultiLine(ushort ad)
        {
            //  遇到JMP,JSR, RTI,RTS 或者分支指令BCC,BCS, BEQ,BNE, BMI,BPL, BVC,BVS 则停止
            List<byte> stopCmd = new List<byte>() {
                0x4C, 0x6C, //  JMP
                0x20,       //  JSR
                0x40,       //  RTI
                0x60,       //  RTS
                0x90, 0xB0, //  BCC, BCS
                0xF0, 0xD0, //  BEQ, BNE
                0x30, 0x10, //  BMI, BPL
                0x50, 0x70  //  BVC, BVS
            };
            byte cmd = 0;

            JumpTo(ad);
            regPanel.PC = ad;

            while ((!stopCmd.Contains(cmd)) && ad < 0xFFFA)
            {
                cmd = cpu.DisassemblySingleStep();
            }
            if (cmd == 0x20) //  Met JSR
            {
                frmEntries.SetEntry(regPanel.PC);
            }
        }

        #endregion

        #region ==============================| 文件读写 |==============================

        /// <summary>
        /// 检查是否有脏数据
        /// </summary>
        /// <returns>是否有脏数据</returns>
        private bool CheckDirty()
        {
            if (Dirty)
            {
                DialogResult r = MessageBox.Show("当前有尚未保存的数据，是否保存？", "数据未保存", MessageBoxButtons.YesNoCancel);
                if (r == DialogResult.Yes)
                {
                    SaveFile(this, null);
                    return Dirty;
                }
                else if (r == DialogResult.Cancel)
                {
                    //  用户放弃退出，仍有脏数据
                    return true;
                }
                //  用户选择No
            }
            //  无脏数据，或者用户选择不保存
            return false;
        }

        /// <summary>
        /// 打开r6a文件
        /// </summary>
        /// <param name="fn">文件名（完整路径）</param>
        private void OpenR6aFile(string fn)
        {
            if (FilePath == String.Empty)
            {
                MessageBox.Show("未指定文件");
                return;
            }
            SaveFilePath = FilePath;
            DA6Accessor ac = new DA6Accessor(regPanel, memory, frmEntries);

            try
            {
                memory.ProgressBar = tssProgressBar;
                memory.DataGridViewVisible = false;
                memory.Reset();
                ac.Read(FilePath);
                memory.DataGridViewVisible = true;
                memory.ProgressBar = null;
                dirty = false;
            }
            catch (System.Exception)
            {
                MessageBox.Show("打开文件失败！");
            }
        }

        /// <summary>
        /// 打开并加载bin文件
        /// </summary>
        /// <param name="fn">文件名（完整路径）</param>
        private void OpenBinFile(string fn)
        {
            if (FilePath == String.Empty)
            {
                MessageBox.Show("未指定文件");
                return;
            }

            BinaryReader br = null;
            FileStream fs = null;
            try
            {
                fs = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
                if (fs.Length > 0xFFFF)
                {
                    MessageBox.Show("bin文件长度不能大于65535字节");
                    return;
                }

                br = new BinaryReader(fs);

                byte[] buf = new byte[fs.Length];
                br.Read(buf, 0, buf.Length);

                DlgBaseAddr dlg = new DlgBaseAddr();
                dlg.SetBaseAddress += SetBaseAddr;
                dlg.ShowDialog(this);

                memory.ProgressBar = tssProgressBar;
                memory.DataGridViewVisible = false;
                memory.Reset();
                memory.LoadImage(memory.BaseAddr, buf);
                memory.DataGridViewVisible = true;
                memory.ProgressBar = null;
            }
            catch (Exception.OutOfRangeException e)
            {
                //  加载地址设置不正则可能造成溢出
                MessageBox.Show(e.Message);
            }
            catch (System.Exception)
            {
                MessageBox.Show("打开文件失败");
            }
            finally
            {
                fs.Close();
                br.Close();
            }
        }

        /// <summary>
        /// 打开并加载NES文件
        /// </summary>
        /// <param name="fn">文件名（完整路径）</param>
        private void OpenNesFile(string fn)
        {
            NESImage nesimg = NESImage.ParseImageFile(fn);

            byte[] buf = new byte[0x8000];
            if (nesimg.NPrgRom == 1)
            {
                nesimg.ProRom.CopyTo(buf, 0);
                nesimg.ProRom.CopyTo(buf, 0x4000);
            }
            else
            {
                nesimg.ProRom.CopyTo(buf, 0);
            }

            memory.ProgressBar = tssProgressBar;
            memory.DataGridViewVisible = false;
            memory.Reset();
            memory.LoadImage(0x8000, buf);
            memory.DataGridViewVisible = true;
            memory.ProgressBar = null;
        }

        /// <summary>
        /// 保存R6A格式文件
        /// </summary>
        private void SaveR6AFile()
        {
            try
            {
                DA6Accessor acc = new DA6Accessor(regPanel, memory, frmEntries);
                tssLabel.Visible = true;
                tssLabel.Text = "保存至" + SaveFilePath;
                acc.Save(SaveFilePath);
                tssLabel.Text = string.Empty;
                tssLabel.Visible = false;
                memory.Dirty = false;
                frmEntries.Dirty = false;
                Text = Properties.Resources.ProjectName + " " + SaveFilePath;
            }
            catch (System.Exception)
            {
            }
        }
        #endregion

        #region ==============================| 窗体相关 |==============================
        private void ToggleMenu(SYSTEM_STATUS s)
        {
            switch (s)
            {
                case SYSTEM_STATUS.FILE_OPENED:
                    mnuSave.Enabled = true;
                    mnuSaveAs.Enabled = true;
                    mnuJumpTo.Enabled = true;
                    mnuMarkAs.Enabled = true;
                    mnuClose.Enabled = true;
                    mnuSingle.Enabled = true;
                    mnuMulti.Enabled = true;
                    mnuEntries.Enabled = true;
                    break;
                case SYSTEM_STATUS.INIT:
                default:
                    mnuSave.Enabled = false;
                    mnuSaveAs.Enabled = false;
                    mnuJumpTo.Enabled = false;
                    mnuMarkAs.Enabled = false;
                    mnuClose.Enabled = false;
                    mnuSingle.Enabled = false;
                    mnuMulti.Enabled = false;
                    mnuEntries.Enabled = false;
                    break;
            }
        }
        #endregion
    }
}